#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the version 3 of the GNU Lesser General Public License
#   as published by the Free Software Foundation.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU Lesser General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Copyright (c) NEC Deutschland GmbH, NEC HPC Europe
#
# $Id$
import logging
from aggmon.aggregator import Aggregator, AggregatorComponent
from basic_types.metric import Metric


class HostSimpleStateAggregator(Aggregator):
    """
    A simple state aggregator for a host metric. It generates a new string (LOG) metric out of an incoming metric.
    Arguments:
    @param metric_name: input metric name
    @param agg_metric_name: aggregated metric name
    @param group_path: needed for subscribing to the correct topics
    @param state_*: state names, example: state_W="WARNING", state_OK="OK", state_C="CRITICAL"
    @param cond_*: logical expression that evaluates to TRUE for a particular state
    @param change_A_B: action to execute when changing from state A to B
    @param arg_*: additional arguments that can be used in the condition expressions
    
    A condition may contain custom arguments (arg_*) and may reference the 'metric'.
    
    The idea is to enable us to use a two staged definition of instances of this aggregator. Use
    preset aggregators to define a pseudo-class, and set there the more generic parameters like
    state_*, cond_*, change_*_*. Then declare instances of these preset-aggregators which get
    the more specific parameters: metric_name, agg_metric_name, arg_*. group_path should be set
    more or less automatically.   
    """
    def __init__( self, metric_name, agg_metric_name, group_path="/", **kwds ):
        self.__group_topic = ".".join( group_path.split("/")[1:] )
        # match any host and any data source 
        topic = self.__group_topic + ( len(self.__group_topic) > 0 and "." or "" ) + "*.*." + metric_name
        #input_channel=None, output_channels=[], topic="#", hierarchy=None
        # assemble arguments for Aggregator class instantiation
        agg_kwds = {}
        for k, v in kwds.items():
            if k in ("input_channel", "output_channels", "topic", "hierarchy"):
                agg_kwds[k] = v
        Aggregator.__init__( self, topic=topic, **agg_kwds )
        self.metric_name = metric_name
        self.agg_metric_name = agg_metric_name
        self.prev_metric = None
        # States
        self._states = {}
        for key, val in kwds.items():
            if key.startswith( "state_" ):
                key = key.lstrip( "state_" )
                self._states[key] = val
        
        # Conditions (must be one expression for each state)
        self._cond = {}
        for state in self._states.keys():
            key = "cond_" + state
            if key in kwds:
                val = kwds[key]
                self._cond[state] = val.replace( "arg_", "self._arg_" )
            else:
                logging.error( "aggregator '%s' definition: missing condition for state '%s'" % (agg_metric_name, state) )
                raise SyntaxError

        # Custom arguments
        for key,val in kwds.items():
            if key.startswith( "arg_" ):
                exec( "self._%s = val" % key )

        # Actions triggered by state changes (not mandatory to have)
        # TODO

    def eval_state( self, metric ):
        "First condition that evaluates to 'True' is returned!"
        for state in self._states.keys():
            condition = self._cond[state]
            logging.debug( "State: %s : %s : %s" % (state, condition, str(eval(condition))) )
            try:
                
                cond = eval( condition )
                
            except:
                cond = False
            if cond is True:
                return state, self._states[state]
        return "UNKNOWN", "UNKNOWN"

    def handleEvent( self, metric, topic=None ):
        #print "max_val aggregator received event:", metric
        if isinstance( metric, Metric ):
            state, out = self.eval_state( metric ) 
            out = out + " (" + metric.name + "=" + str( metric.value ) + ")"
            topic = "aggregator." + self.agg_metric_name
            params = { "name"   : self.agg_metric_name,
                       "value"  : state,
                       "output" : out,
                       "source" : "aggregator",
                       "time"   :  metric.time }
            if hasattr( metric, "host" ):
                params["host"] = metric.host
                topic = self.__group_topic + ( len(self.__group_topic) > 0 and "." or "" ) \
                        + metric.host + "." + topic
            agg_metric = Metric( **params )
            
            # check if this is something new... i.e. agg_metric changed since the last time
            if self.prev_metric is not None and self.prev_metric.value == agg_metric.value \
                and self.prev_metric.output == agg_metric.output:
                return
            else:
                self.prev_metric = agg_metric

            # TODO: Actors and actions

            self.publish( topic, agg_metric )


class HostSimpleStateAggregatorComponent(AggregatorComponent):
    def instantiate( self, *args, **kwds ):
        self.instance = None
        try:
            logging.debug( "instantiating: %s" % self.name )
            self.instance = HostSimpleStateAggregator( *args, **kwds )
        except Exception, e:
            raise e
